﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MentalAlchemy.Atomics;

namespace MentalAlchemy.Molecules.MachineLearning
{
	public class EvoPCA : NeuroEvolutionaryAlgorithm
	{
		public bool VariableMutationStep;
		public int DataAmount = 100;	// amount (%) of training data to be used.

		public EvoPCA ()
		{
			Evaluation = Evaluate;
			Mutation = Mutate;
			Crossing = EAElements.WeightedCross;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Method for evaluation of the population.
		/// </summary>
		/// <param name="popul"></param>
		/// <param name="fitFunc"></param>
		/// <param name="best"></param>
		public virtual void Evaluate(List<AbstractIndividual> popul, FitnessFunction fitFunc, out Individual best)
		{
			var outputs = NeuralNetwork.OutputsNumber;

			// get orthonormalized weights for all individuals.
			var orthWeights = new List<List<float[]>>();
			var nWeights = new List<float[]>();
			for (var i = 0; i < popul.Count; i++)
			{
				var ind = (Individual)popul[i];
				var wrows = VectorMath.Split(ind.Genes.ToArray(), outputs);	// nodes weights.

				// perform Gramm-Schmidt orthogonalization.
				orthWeights.Add(Numerics.GramSchmidtOrthonormalization(wrows));

				// computation of fitness.
				var wm = MatrixMath.CreateFromRowsList(orthWeights[i]);
				nWeights.Add(MatrixMath.ConvertToVector(wm));
			}

			//
			// compute fitness and find the most fit individual.
			//var fits = Performance.MachineLearning.EvaluateNetworks(fitFunc, nWeights);
			//var fits2 = Performance.MachineLearning.EvaluateNetworks(fitFunc, nWeights);

			var fits = new Fitness[popul.Count];
			for (int i = 0; i < popul.Count; ++i)
			{
				fits[i] = fitFunc.Compute(nWeights[i]);
			}

			var minF = new Fitness();
			best = (Individual)popul[0];
			for (var i = 0; i < popul.Count; i++)
			{
				var ind = (Individual)popul[i];
				ind.Fitness = fits[i];

				#region - Update best fitness. -
				if (i != 0)
				{
					if (FitnessComparator.IsBetter(ind.Fitness, minF))
					{
						minF = ind.Fitness;
						best = ind;
					}
				}
				else
				{
					minF = popul[0].Fitness;
					best = (Individual)popul[0];
				}
				#endregion
			}

			//
			// process variances info.
			for (var i = 0; i < popul.Count; i++)
			{
				var ind = (Individual)popul[i];

				//
				// extract info about variances.
				var vars = ind.Fitness.Extra;
				
				// sort weights vectors by variances in descending order and rewrite individual.
				var dict = new Dictionary<float[], float>();
				for (int j = 0; j < outputs; j++)
				{
					dict.Add(orthWeights[i][j], vars[j]);
				}
				dict = StructMath.SortByValue(dict);

				// update temporal weights container.
				var wrows = new List<float[]>(dict.Keys);
				wrows = VectorMath.Reverse(wrows);	// reverse rows order since sorting was performed in ascending order.

				// update individual genes.
				var wm = MatrixMath.CreateFromRowsList(wrows);
				ind.Genes.Clear();
				ind.Genes.AddRange(MatrixMath.ConvertToVector(wm));

				//
				// update extras.
				ind.Fitness.Extra = new List<float>(dict.Values);
				ind.Fitness.Extra = VectorMath.Reverse(ind.Fitness.Extra);	// reverse order since sorting was performed in ascending order.
			}
			best = (Individual)best.Clone();	// to remove double pointing to the same object and occasional change of the best found individual.
		}

		public void Mutate(List<AbstractIndividual> popul, EAParameters pars)
		{
			// Update mutation step.
			pars.MutationStep = VariableMutationStep ? (float) Math.Exp(-CurrentGeneration) : 0.1f;

			EAElements.Mutate(popul, pars);
		}
	}
}
